/*
 * MIT License
 *
 * Copyright (c) 2023 北京凯特伟业科技有限公司
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.je.ibatis.extension.builder;

import com.je.ibatis.builder.xml.CustomXMLMapperBuilder;
import com.je.ibatis.extension.cache.DefaultMetaDataCacheManager;
import com.je.ibatis.extension.cache.MetaDataCacheManager;
import com.je.ibatis.extension.enums.DbType;
import com.je.ibatis.extension.keygen.MetaAutoKeyGenerator;
import com.je.ibatis.extension.keygen.MetaRouterKeyGenerator;
import com.je.ibatis.extension.keygen.MetaSelectKeyGenerator;
import com.je.ibatis.extension.keygen.MetaUUIDKeyGenerator;
import com.je.ibatis.extension.keygen.sql.KeySqlGenerator;
import com.je.ibatis.extension.metadata.IdType;
import com.je.ibatis.extension.metadata.model.Function;
import com.je.ibatis.extension.metadata.model.Id;
import com.je.ibatis.extension.metadata.model.Table;
import com.je.ibatis.extension.parse.DefaultMetaDataParse;
import com.je.ibatis.extension.parse.MetaDataParse;
import com.je.ibatis.extension.toolkit.Constants;
import com.je.ibatis.session.CustomConfiguration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.builder.StaticSqlSource;
import org.apache.ibatis.cache.CacheException;
import org.apache.ibatis.executor.keygen.KeyGenerator;
import org.apache.ibatis.executor.keygen.NoKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ResultMap;
import org.apache.ibatis.mapping.SqlCommandType;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Optional;

/**
 * 元数据 MappedStatement 构建器
 *
 * @author wangmm@ketr.com.cn
 * @date 2019/11/21
 */
public class MetaStatementBuilder {
    private static final Log LOGGER = LogFactory.getLog(MetaStatementBuilder.class);

    protected final CustomConfiguration configuration;
    protected final MetaDataParse parse;
    private final MetaDataCacheManager cacheManager;

    public static MetaStatementBuilder getInstance(CustomConfiguration configuration, MetaDataParse parse, MetaDataCacheManager cacheManager) {
        return new MetaStatementBuilder(configuration,
                Optional.ofNullable(parse).orElse(new DefaultMetaDataParse()),
                Optional.ofNullable(cacheManager).orElse(new DefaultMetaDataCacheManager())
        );
    }

    public MetaStatementBuilder(CustomConfiguration configuration, MetaDataParse parse, MetaDataCacheManager cacheManager) {

        this.configuration = configuration;
        //元数据解析器
        this.parse = parse;
        //元数据缓存管理器
        this.cacheManager = cacheManager;
        //设置解析器数据源
        this.parse.setDataSource(configuration.getEnvironment().getDataSource());
        //设置解析器数据源
        this.cacheManager.setConfiguration(configuration);
    }

    /**
     * 预加载核心Mapper
     *
     * @return this
     */
    public MetaStatementBuilder builder() {

        try {
            //解析基础Mapper文件 --> baseMapper.xml
            new CustomXMLMapperBuilder(Resources.getResourceAsStream(Constants.BASE_MAPPER_LOCATION),
                    configuration, Constants.BASE_MAPPER_LOCATION, configuration.getSqlFragments()).parse();
            //解析基础Mapper文件
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return this;
    }

    /**
     * 重新获取 table 元数据
     *
     * @param tableCode 表名
     * @return com.je.ibatis.extension.metadata.model.Table
     */
    public Table builderTable(String tableCode) {
        cacheManager.removeTable(tableCode);
        return table(tableCode);
    }

    /**
     * 获取 table 元数据
     *
     * @param tableCode 表名
     * @return com.je.ibatis.extension.metadata.model.Table
     */
    public Table table(String tableCode) {
        //校验是否为null
        Optional.ofNullable(tableCode).orElseThrow(() -> new CacheException("Meta tableCode is null."));
        //缓存中获取
        Table table = cacheManager.getTable(tableCode);
        if (table == null) {
            LOGGER.debug("Meta table[" + tableCode + "] cache init.");
            //查询元数据
            table = parse.table(tableCode);
            Optional.ofNullable(table).orElseThrow(() -> new CacheException("Meta table[" + tableCode + "] parse fail."));
            //put缓存
            cacheManager.put(table);
            //生成主键策略
            keyGen(table);
        }
        return table;
    }

    /**
     * 重新获取 Function 元数据
     *
     * @param functionCode 功能名称
     * @return com.je.ibatis.extension.metadata.model.Table
     */
    public Function builderFunction(String functionCode) {
        cacheManager.removeFunction(functionCode);
        return function(functionCode);
    }

    /**
     * 获取 Function 元数据
     *
     * @param functionCode 功能名称
     * @return com.je.ibatis.extension.metadata.model.Table
     */
    public Function function(String functionCode) {
        //校验是否为null
        Optional.ofNullable(functionCode).orElseThrow(() -> new CacheException("Meta functionCode is null."));
        //缓存中获取
        Function function = cacheManager.getFunction(functionCode);
        if (function == null) {

            LOGGER.debug("Meta function[" + functionCode + "] cache init.");
            //查询元数据
            function = parse.function(functionCode);
            Optional.ofNullable(function).orElseThrow(() -> new CacheException("Meta function[" + functionCode + "] parse fail."));

            //获取 table
            Table table = null;
            try {
                //如果存在表名，则查询缓存
                if (!StringUtils.isEmpty(function.getTableCode())) {
                    table = table(function.getTableCode());
                    function.setTable(table);
                }
            } catch (Throwable t) {
                LOGGER.debug("Meta function table[" + function.getTableCode() + "] cache init fail.");
                return function;
            }
            //填充功能相关表信息
            cacheManager.put(function);
        }
        return function;
    }

    /**
     * 生成主键策略
     *
     * @param table 资源表表元数据
     * @return org.apache.ibatis.executor.keygen.KeyGenerator
     */
    private KeyGenerator keyGen(Table table) {

        KeyGenerator keyGenerator;

        // 获取表格主键
        Id id = table.getId();
        //没有主键使用默认无主键策略
        if (id == null || id.getType() == IdType.NONE) {
            keyGenerator = NoKeyGenerator.INSTANCE;
        } else {
            //获取主键类型
            IdType type = id.getType();
            if (type == null) {
                throw new RuntimeException(String.format("Table(%s)-Key(%s) IdType is null ", table.getCode(), id.getCode()));
            } else if (type == IdType.UUID) {
                keyGenerator = new MetaUUIDKeyGenerator(table.getCode(), id.getCode());
            } else if (type == IdType.AUTO) {
                /*if (CustomConfiguration.dbType != DbType.MYSQL) {
                    throw new RuntimeException(String.format("Databases other than mysql are not supported (AUTO) "));
                }*/
                keyGenerator = new MetaAutoKeyGenerator(table.getCode(), id.getCode());
            } else if (type == IdType.SQL) {
                //获取生成器参数,如Oracle序列名称
                String sql = id.getSql();
                if (sql != null && !StringUtils.isEmpty(sql)) {
                    //生成Select主键策略
                    String generatorId = table.getCode() + MetaRouterKeyGenerator.SELECT_KEY_SUFFIX;
                    ResultMap resultMap = new ResultMap.Builder(configuration, generatorId, String.class, new ArrayList<>()).build();
                    MappedStatement mappedStatement = new MappedStatement.Builder(configuration, generatorId,
                            new StaticSqlSource(configuration, sql), SqlCommandType.SELECT)
                            .resultMaps(Collections.singletonList(resultMap))
                            .build();
                    configuration.addMappedStatement(mappedStatement, true);
                    keyGenerator = new MetaSelectKeyGenerator(table.getCode(), id.getCode(), mappedStatement);
                } else {
                    keyGenerator = new MetaAutoKeyGenerator(table.getCode(), id.getCode());
                }
            } else if (type == IdType.AUTO_INCREMENTER) {
                //获取Sql主键生成器
                KeySqlGenerator keySqlGenerator = CustomConfiguration.dbType.getKeySqlGenerator();
                //获取生成器参数,如Oracle序列名称
                String incrementerName = id.getIncrementerName();
                if (keySqlGenerator != null && !StringUtils.isEmpty(incrementerName)) {
                    //生成Select主键策略
                    String generatorId = table.getCode() + MetaRouterKeyGenerator.SELECT_KEY_SUFFIX;
                    ResultMap resultMap = new ResultMap.Builder(configuration, generatorId, String.class, new ArrayList<>()).build();
                    MappedStatement mappedStatement = new MappedStatement.Builder(configuration, generatorId,
                            new StaticSqlSource(configuration, keySqlGenerator.generateSql(incrementerName)), SqlCommandType.SELECT)
                            .resultMaps(Collections.singletonList(resultMap))
                            .build();
                    configuration.addMappedStatement(mappedStatement, true);
                    keyGenerator = new MetaSelectKeyGenerator(table.getCode(), id.getCode(), mappedStatement);
                } else if (keySqlGenerator != null && StringUtils.isEmpty(incrementerName)) {
                    throw new RuntimeException(String.format("Table(%s)-Key(%s) incrementerName is invalid ", table.getCode(), id.getCode()));
                } else {
                    keyGenerator = new MetaAutoKeyGenerator(table.getCode(), id.getCode());
                }
            } else {
                throw new RuntimeException(String.format("Table(%s)-Key(%s) IdType is invalid ", table.getCode(), id.getCode()));
            }
        }

        //主键唯一标识
        String generatorId = table.getCode().toLowerCase() + MetaRouterKeyGenerator.SELECT_KEY_SUFFIX;
        String generatorIdUp = table.getCode().toUpperCase() + MetaRouterKeyGenerator.SELECT_KEY_SUFFIX;

        //加入KeyGenerators集合
        configuration.addKeyGenerator(generatorId, keyGenerator, true);
        configuration.addKeyGenerator(generatorIdUp, keyGenerator, true);

        return keyGenerator;
    }

    public CustomConfiguration getConfiguration() {
        return configuration;
    }

    public MetaDataCacheManager getCacheManager() {
        return cacheManager;
    }

    public MetaDataParse getParse() {
        return parse;
    }
}